# Purpose: Loads and provides access to application configuration from YAML files.
# Changes:
# - No major functional changes from the previous correct version.
# - Added explicit type hints.
# - Ensured robust path calculation.

import os
from pathlib import Path
import yaml
from typing import Any, Dict, List, Optional
import logging

from .exceptions import ConfigError

logger = logging.getLogger(__name__)

# Assume this file is at src/core/config.py
# Project Root is Path(__file__).parent.parent.parent
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
DEFAULT_CONFIG_DIR = PROJECT_ROOT / "config"

_config_cache: Optional[Dict[str, Any]] = None

def _determine_config_files() -> List[Path]:
    """Determines the list of config files to load based on APP_ENV."""
    files_to_load: List[Path] = []
    default_path = DEFAULT_CONFIG_DIR / "default.yaml"

    if default_path.exists():
        files_to_load.append(default_path)
    else:
        logger.warning(f"Default configuration file not found: {default_path}")

    env = os.getenv("APP_ENV", "development").lower()
    env_config_file = DEFAULT_CONFIG_DIR / f"{env}.yaml"

    if env_config_file.exists():
        files_to_load.append(env_config_file)
        logger.debug(f"Environment-specific config file found: {env_config_file.name}")
    elif env != "development": # Don't warn if default dev file is missing and env is dev
         logger.warning(f"Environment-specific config file not found for env '{env}': {env_config_file}")

    if not files_to_load:
        logger.warning("No configuration files found (default.yaml or environment-specific).")

    return files_to_load

def _deep_merge(source: Dict[str, Any], destination: Dict[str, Any]) -> Dict[str, Any]:
    """Recursively merges source dict into destination dict."""
    for key, value in source.items():
        if isinstance(value, dict):
            node = destination.setdefault(key, {})
            if isinstance(node, dict):
                 _deep_merge(value, node)
            else:
                 destination[key] = value # Overwrite if types conflict (dict vs non-dict)
        else:
            destination[key] = value
    return destination

def load_config(force_reload: bool = False) -> Dict[str, Any]:
    """
    Loads and merges configuration from YAML files based on APP_ENV.
    Uses a cache unless force_reload is True.

    Args:
        force_reload: If True, bypasses the cache and reloads from files.

    Returns:
        A dictionary containing the merged configuration.

    Raises:
        ConfigError: If configuration loading fails critically.
    """
    global _config_cache
    if _config_cache is not None and not force_reload:
        logger.debug("Returning cached configuration.")
        return _config_cache

    config_files = _determine_config_files()
    merged_config: Dict[str, Any] = {}
    files_loaded_count = 0
    logger.info(f"Attempting to load configuration from: {[p.name for p in config_files]}")

    for config_path in config_files:
        # Check existence again in case something changed
        if not config_path.exists():
            logger.debug(f"Skipping non-existent config file: {config_path}")
            continue
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                config_data = yaml.safe_load(f)
                if isinstance(config_data, dict):
                    logger.debug(f"Merging valid config from {config_path.name}...")
                    merged_config = _deep_merge(config_data, merged_config)
                    files_loaded_count += 1
                elif config_data is not None:
                    logger.warning(f"Config file {config_path.name} does not contain a dictionary at the top level, skipping merge.")
                else:
                     logger.debug(f"Config file {config_path.name} is empty or contains only null.")
        except yaml.YAMLError as e:
            logger.error(f"Error parsing YAML file {config_path}: {e}", exc_info=True)
            raise ConfigError(f"Invalid YAML format in {config_path}") from e
        except Exception as e:
            logger.error(f"Error reading config file {config_path}: {e}", exc_info=True)
            raise ConfigError(f"Could not read config file {config_path}") from e

    if files_loaded_count == 0:
        logger.error("No valid configuration files were loaded.")
        # Return empty dict, but log error. Downstream code should handle missing config.
        # raise ConfigError("No configuration loaded.")

    _config_cache = merged_config
    current_env = os.getenv('APP_ENV', 'development')
    logger.info(f"Configuration loading process completed. Environment: '{current_env}'. Files loaded: {files_loaded_count}.")
    return merged_config

def get_config() -> Dict[str, Any]:
    """Returns the loaded configuration dictionary (loads if not cached)."""
    # Ensure load_config runs at least once
    if _config_cache is None:
        return load_config()
    return _config_cache

def get_config_value(key_path: str, default: Any = None) -> Any:
    """
    Retrieves a configuration value using a dot-separated key path.
    e.g., get_config_value('skills.web_automation.default_browser')

    Args:
        key_path: Dot-separated string representing the nested key.
        default: The value to return if the key is not found.

    Returns:
        The configuration value or the default.
    """
    config = get_config() # Ensures config is loaded
    keys = key_path.split('.')
    value = config
    current_path = []
    try:
        for key in keys:
            current_path.append(key)
            if isinstance(value, dict):
                value = value[key] # Raises KeyError if key missing
            else:
                # Attempted to access a key on a non-dictionary item during traversal
                intermediate_path = ".".join(current_path[:-1])
                logger.warning(f"Config path '{key_path}' requires '{key}' but '{intermediate_path}' is not a dictionary (type: {type(value)}).")
                return default
        return value
    except KeyError:
        # Key not found at the expected level
        logger.debug(f"Config key '{key_path}' not found. Returning default: {repr(default)}")
        return default
    except Exception as e:
        # Catch unexpected errors during access
        logger.error(f"Unexpected error accessing config key '{key_path}': {e}", exc_info=True)
        return default